/**
 * Copyright 2009-2011 the original author or authors.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.myjung.dao;

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import net.myjung.Myjung;
import net.myjung.exception.MyException;
import net.myjung.model.BaseObject;
import net.myjung.model.Query;
import net.myjung.model.Filter;
import net.myjung.model.Filters;
import net.myjung.model.Order;
import net.myjung.model.QueryParameters;
import net.myjung.model.TransferObject;
import net.myjung.model.annotation.MdlAttribute;
import net.myjung.model.annotation.MdlElement;
import net.myjung.model.annotation.MdlField;
import net.myjung.model.annotation.MdlText;
import net.myjung.model.util.IdGenerator;
import net.sf.common.util.ValueUtils;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.SQLQuery;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import org.springframework.util.StringUtils;

/**
 * @author Steve M. Jung
 */
public abstract class AbstractDaoHibernate extends HibernateDaoSupport implements IDao {
	protected final Log logger = LogFactory.getLog(getClass());

	protected <T extends BaseObject> T get(Class<T> clazz, String id, Map<String, Object> options) throws Exception {
		Field idField = IdGenerator.getIdField(clazz);
		if (idField == null)
			return null;
		Query conds = new Query();
		conds.addFilter(new Filter(idField.getName(), id));
		List<T> list = getList(clazz, conds, options);
		return Myjung.isEmpty(list) ? null : list.get(0);
	}
	protected void set(String user, BaseObject obj, Map<String, Object> options) throws Exception {
		if (obj == null)
			return;
		populate(user, obj, getOld(obj));
		getHibernateTemplate().saveOrUpdate(obj);
	}
	protected void create(String user, BaseObject obj, Map<String, Object> options) throws Exception {
		if (obj == null)
			return;
		if (getOld(obj) != null)
			throw new MyException(new StringBuffer("Duplicate object: ").append(obj).toString());
		populate(user, obj, null);
		getHibernateTemplate().save(obj);
	}
	protected void update(String user, BaseObject obj, Map<String, Object> options) throws Exception {
		if (obj == null)
			return;
		populate(user, obj, getOld(obj));
		getHibernateTemplate().update(obj);
	}
	protected BaseObject delete(Class<? extends BaseObject> clazz, String id, Map<String, Object> options) throws Exception {
		return delete(get(clazz, id, options));
	}
	@SuppressWarnings({ "unchecked", "rawtypes" })
	protected <T extends BaseObject> List<T> getList(Class<T> clazz, Query conds, Map<String, Object> options) throws Exception {
		StringBuffer buf = new StringBuffer();
		buf.append("select");
		List<Field> fieldList = null;
		String level = LEVEL_ALL;
		String[] fields = null;
		if (!Myjung.isEmpty(options)) {
			level = options.containsKey("level") ? (String) options.get("level") : LEVEL_ALL;
			fields = options.containsKey("fields") ? StringUtils.tokenizeToStringArray((String) options.get("fields"), ",") : null;
		}
		if (!Myjung.isEmpty(fields)) {
			fieldList = new ArrayList<Field>();
			int i = 0;
			for (String f : fields) {
				Field field = Myjung.getField(clazz, f);
				if (field != null)
					fieldList.add(field);
				buf.append(i++ == 0 ? " obj." : ", obj.").append(f);
			}
		} else if (!Myjung.isEmpty(conds.getGroup())) {
			fieldList = new ArrayList<Field>();
			int i = 0;
			for (String group : conds.getGroup()) {
				Field field = Myjung.getField(clazz, group);
				if (field != null)
					fieldList.add(field);
				buf.append(i++ == 0 ? " obj." : ", obj.").append(group);
			}
		} else if (level.equals(LEVEL_ALL)) {
			buf.append(" obj");
		} else {
			fieldList = new ArrayList<Field>();
			appendSelectField(buf, clazz, fieldList, level);
		}
		org.hibernate.Query query = appendQuery(buf, clazz, conds);
		List list = query.list();
		if (Myjung.isEmpty(list))
			return list;
		if (Myjung.isEmpty(fields) && Myjung.isEmpty(conds.getGroup()) && level.equals(LEVEL_ALL)) {
			return (List<T>) list;
		}
		List<T> objList = new ArrayList<T>();
		if (fieldList.size() == 1) {
			Field field = fieldList.get(0);
			if (!field.isAccessible())
				field.setAccessible(true);
			T obj = clazz.newInstance();
			objList.add(obj);
			for (Object data : list)
				field.set(obj, data);
		} else {
			for (Object[] datum : (List<Object[]>) list) {
				T obj = clazz.newInstance();
				objList.add(obj);
				int i = 0;
				for (Field field : fieldList) {
					if (!field.isAccessible())
						field.setAccessible(true);
					field.set(obj, datum[i++]);
				}
			}
		}
		return objList;
	}
	protected long getSize(Class<? extends BaseObject> clazz, Query conds, Map<String, Object> options) throws Exception {
		StringBuffer buf = new StringBuffer();
		buf.append("select");
		buf.append(" count(*)");
		int pageNo = 0;
		int pageSize = 0;
		if (conds != null) {
			pageNo = conds.getPageIndex();
			pageSize = conds.getPageSize();
			conds.setPageIndex(0);
			conds.setPageSize(-1);
		}
		org.hibernate.Query query = appendQuery(buf, clazz, conds);
		if (conds != null) {
			conds.setPageIndex(pageNo);
			conds.setPageSize(pageSize);
		}
		return (Long) query.list().get(0);
	}
	protected <T extends BaseObject> List<T> deleteList(Class<T> clazz, Query conds, Map<String, Object> options) throws Exception {
		List<T> list = getList(clazz, conds, options);
		if (Myjung.isEmpty(list))
			return list;
		getHibernateTemplate().deleteAll(list);
		return list;
	}

	private static void appendSelectField(StringBuffer buf, Class<? extends BaseObject> clazz, List<Field> fieldList, String level) {
		appendFields(clazz, fieldList, level);
		if (Myjung.isEmpty(fieldList))
			return;
		int i = 0;
		for (Field field : fieldList) {
			if (i++ > 0)
				buf.append(",");
			buf.append(" obj.").append(field.getName());
		}
	}
	@SuppressWarnings("unchecked")
	private static void appendFields(Class<? extends BaseObject> clazz, List<Field> fieldList, String level) {
		if (clazz == null || clazz == BaseObject.class)
			return;
		appendFields((Class<? extends BaseObject>) clazz.getSuperclass(), fieldList, level);
		Field[] fields = clazz.getDeclaredFields();
		if (Myjung.isEmpty(fields))
			return;
		for (Field field : fields) {
			Annotation[] anns = field.getAnnotations();
			if (Myjung.isEmpty(anns)) {
				fieldList.add(field);
				continue;
			}
			for (Annotation ann : anns) {
				if (ann instanceof MdlAttribute) {
					MdlAttribute fann = (MdlAttribute) ann;
					if (fann.column().equals("disabled"))
						continue;
					fieldList.add(field);
				} else if (ann instanceof MdlElement) {
					MdlElement fann = (MdlElement) ann;
					if (fann.column().equals("disabled") || fann.column().indexOf(".") != -1)
						continue;
					Class<?> type = field.getType();
					if (type != String.class && type != Character.class && type != Boolean.class && type != Integer.class && type != Long.class
							&& type != Float.class && type != Double.class)
						continue;
					fieldList.add(field);
				} else if (ann instanceof MdlText) {
					MdlText fann = (MdlText) ann;
					if (fann.column().equals("disabled"))
						continue;
					fieldList.add(field);
				} else if (ann instanceof MdlField) {
					MdlField fann = (MdlField) ann;
					if (fann.column().equals("disabled") || fann.column().indexOf(".") != -1)
						continue;
					fieldList.add(field);
				}
			}
		}
	}
	private org.hibernate.Query appendQuery(StringBuffer buf, Class<? extends BaseObject> clazz, Query conds) throws Exception {
		// from
		buf.append(" from ").append(clazz.getName()).append(" obj");

		// where
		Map<String, Object> paramMap = appendWhereQuery(buf, clazz, conds);

		// group by
		appendGroupQuery(buf, conds);

		// order by
		appendOrderQuery(buf, conds);

		// create query
		org.hibernate.Query query = getSession().createQuery(buf.toString());
		if (conds == null)
			return query;

		// set params
		setParams(query, paramMap);

		// pagination
		populatePagination(query, conds.getPageSize(), conds.getPageIndex());

		return query;
	}
	private static void populatePagination(org.hibernate.Query query, int pageSize, int pageNo) {
		if (pageSize < 1 || pageNo < 0)
			return;
		query.setFirstResult(pageNo * pageSize);
		query.setMaxResults(pageSize);
	}
	private static Map<String, Object> appendWhereQuery(StringBuffer buf, Class<? extends BaseObject> clazz, Query conds) throws Exception {
		if (conds == null)
			return null;
		Map<String, Object> paramMap = new HashMap<String, Object>();
		StringBuffer whereBuf = new StringBuffer();
		appendWhereQuery(whereBuf, clazz, conds, paramMap, 0);
		if (whereBuf.length() != 0)
			buf.append(" where ").append(whereBuf);
		return paramMap;
	}

	private static Pattern methodPattern = Pattern.compile("\\b\\S+\\b\\s*\\([^(]*\\)");
	private static Set<String> operatorSet = new HashSet<String>();
	private static final String[] operators = { "||" };
	private static int appendWhereQuery(StringBuffer buf, Class<? extends BaseObject> clazz, Filters filters, Map<String, Object> paramMap, int i)
			throws Exception {
		if (filters == null)
			return i;

		String fsOper = filters.getOperator();
		int tmpI = i;

		List<Filter> filterList = filters.getFilter();
		if (!Myjung.isEmpty(filterList)) {
			for (Filter filter : filterList) {
				String leftOperand = filter.getLeftOperand();

				Map<String, String> methodMap = null;
				if (leftOperand.indexOf("(") != -1 || leftOperand.indexOf(")") != -1) {
					methodMap = new HashMap<String, String>();
					Matcher matcher = methodPattern.matcher(leftOperand);
					for (int k = 0; matcher.find(k); k = matcher.end()) {
						String method = matcher.group();
						String[] methods = StringUtils.tokenizeToStringArray(method, "(");
						int methodLength = methods.length;
						if (methodLength > 1) {
							StringBuffer methodBuf = new StringBuffer();
							int l = 0;
							String methodName = methods[l++];
							methodBuf.append(methodName).append("(");
							if (methodName.equals("substring"))
								methodBuf.append("obj.");
							for (; l < methodLength; l++)
								methodBuf.append(methods[l]);
							String methodId = new StringBuffer("${").append(Myjung.newId()).append("}").toString();
							leftOperand = StringUtils.replace(leftOperand, method, methodId);
							methodMap.put(methodId, methodBuf.toString());
						}
					}
				}

				String[] lefts = StringUtils.tokenizeToStringArray(leftOperand, " \t");
				String rightType = Myjung.toString(filter.getRightOperandType(), Filter.OPERANDTYPE_VALUE);
				List<String> right = filter.getRightOperand();
				String operator = filter.getOperator();
				if (i++ != tmpI)
					buf.append(" ").append(fsOper).append(" ");
				if (lefts.length > 1) {
					if (operatorSet.isEmpty()) {
						synchronized (operatorSet) {
							if (operatorSet.isEmpty()) {
								for (String oper : operators)
									operatorSet.add(oper);
							}
						}
					}
					int j = 0;
					for (String left : lefts) {
						buf.append(j++ == 0 ? "" : " ");

						if (operatorSet.contains(left) || left.startsWith("${") || (left.startsWith("'") && left.endsWith("'"))) {
							buf.append(left);
							continue;
						}

						if (left.indexOf("(") == -1 || left.indexOf(")") == -1) {
							buf.append("obj.").append(left);
							continue;
						}
					}
				} else {
					buf.append("obj.").append(lefts[0]);
				}
				if (!Myjung.isEmpty(methodMap)) {
					leftOperand = buf.toString();
					for (String methodId : methodMap.keySet())
						StringUtils.replace(leftOperand, methodId, methodMap.get(methodId));
					buf = new StringBuffer(leftOperand);
				}

				if (Myjung.isEmpty(right)) {
					buf.append(" is null");
				} else {
					int rightSize = right.size();
					if (rightSize == 1 && right.get(0) == null) {
						buf.append(" is null");
						continue;
					}

					if (rightSize > 1 || Filter.OPERANDTYPE_QUERY.equals(rightType))
						operator = "in";
					buf.append(" ").append(operator).append(" ");
					Class<?> type = null;
					if (lefts.length == 1) {
						String left = lefts[0];
						Field field = getField(clazz, left);
						if (field == null)
							throw new MyException(new StringBuffer("Couldn't find Field:").append(left).append(" from Class:")
									.append(clazz.getName()).toString());
					} else {
						type = String.class;
					}
					appendValueFilter(buf, paramMap, clazz, type, rightType, right, i);
				}
			}
		}

		List<Filters> subFiltersList = filters.getFilters();
		if (!Myjung.isEmpty(subFiltersList)) {
			boolean isFirst = tmpI == i;
			for (Filters subFilters : subFiltersList) {
				StringBuffer filtersBuf = new StringBuffer();
				tmpI = i;
				i = appendWhereQuery(filtersBuf, clazz, subFilters, paramMap, i);
				if (tmpI == i)
					continue;
				if (isFirst)
					isFirst = false;
				else
					buf.append(" ").append(fsOper).append(" ");
				buf.append("(").append(filtersBuf).append(")");
			}
		}

		return i;
	}
	private static void appendValueFilter(StringBuffer buf, Map<String, Object> paramMap, Class<? extends BaseObject> clazz, Class<?> type,
			String valueType, List<String> values, int i) throws Exception {

		if (Filter.OPERANDTYPE_QUERY.equals(valueType)) {
			String value = values.get(0).trim();
			if (value.startsWith("("))
				buf.append(value);
			else
				buf.append("(").append(value).append(")");
			return;
		}

		if (values.size() == 1) {
			appendValueFilter(buf, paramMap, type, values.get(0), i, null);
		} else {
			buf.append("(");
			int j = 0;
			for (String value : values) {
				if (j != 0)
					buf.append(",");
				appendValueFilter(buf, paramMap, type, value, i, "_" + j++);
			}
			buf.append(")");
		}
	}
	private static void appendValueFilter(StringBuffer buf, Map<String, Object> paramMap, Class<?> type, String valueStr, int i, String suffix)
			throws Exception {
		if (valueStr == null)
			return;
		String key = new StringBuffer("param").append(i).append(suffix == null ? "" : suffix).toString();
		Object value = null;
		if (type == String.class) {
			value = valueStr;
		} else if (type == Date.class) {
			value = Myjung.toDate(valueStr);
		} else if (type == Boolean.class) {
			value = Myjung.toBoolean(valueStr);
		} else if (type == Integer.class) {
			value = new Integer(valueStr);
		} else if (type == Long.class) {
			value = new Long(valueStr);
		} else if (type == Float.class) {
			value = new Float(valueStr);
		} else if (type == Double.class) {
			value = new Double(valueStr);
		} else if (type == Character.class) {
			value = new Character(valueStr.charAt(0));
		} else {
			value = valueStr;
		}
		buf.append(":").append(key);
		paramMap.put(key, value);
	}
	private static Class<?> toGenericType(Field field) {
		Class<?> type = field.getType();
		if (!Collection.class.isAssignableFrom(type) || !(field.getGenericType() instanceof ParameterizedType))
			return type;
		return (Class<?>) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0];
	}
	private static Field getField(Class<?> clazz, String path) {
		Field field = null;
		String[] paths = StringUtils.tokenizeToStringArray(path, ".");
		for (String name : paths) {
			field = Myjung.getField(clazz, name);
			if (field == null)
				return null;
			clazz = toGenericType(field);
		}
		return field;
	}
	private static void setParams(org.hibernate.Query query, Map<String, Object> paramMap) {
		if (Myjung.isEmpty(paramMap))
			return;
		Set<String> keys = paramMap.keySet();
		for (String key : keys) {
			Object data = paramMap.get(key);
			if (data instanceof String) {
				query.setString(key, (String) data);
			} else if (data instanceof Date) {
				query.setTimestamp(key, (Date) data);
			} else if (data instanceof Integer) {
				query.setInteger(key, (Integer) data);
			} else if (data instanceof Long) {
				query.setLong(key, (Long) data);
			} else if (data instanceof Float) {
				query.setFloat(key, (Float) data);
			} else if (data instanceof Double) {
				query.setDouble(key, (Double) data);
			} else if (data instanceof Character) {
				query.setCharacter(key, (Character) data);
			} else {
				query.setParameter(key, data);
			}
		}
	}
	private static void appendGroupQuery(StringBuffer buf, Query conds) {
		if (conds == null)
			return;
		List<String> groups = conds.getGroup();
		if (Myjung.isEmpty(groups))
			return;
		int i = 0;
		for (String group : groups)
			buf.append(i++ == 0 ? " group by obj." : ", obj.").append(group);
	}
	private static void appendOrderQuery(StringBuffer buf, Query conds) {
		if (conds == null)
			return;
		List<Order> orders = conds.getOrder();
		if (Myjung.isEmpty(orders))
			return;
		int i = 0;
		for (Order order : orders) {
			if (Myjung.isEmpty(order.getField()))
				continue;
			buf.append(i++ == 0 ? " order by obj." : ", obj.").append(order.getField()).append(order.isAsc() ? " asc" : " desc");
		}
	}
	private BaseObject getOld(BaseObject obj) throws Exception {
		Field idField = IdGenerator.getIdField(obj.getClass());
		if (idField == null)
			return null;
		if (!idField.isAccessible())
			idField.setAccessible(true);
		Serializable id = (Serializable) idField.get(obj);
		if (id == null)
			return null;
		BaseObject oldObj = (BaseObject) getHibernateTemplate().load(obj.getClass(), id);
		if (oldObj == null || !Myjung.isEqual(id, idField.get(oldObj)))
			return null;
		return oldObj;
	}
	private void populate(String user, BaseObject obj, BaseObject old) throws Exception {
		Class<?> clazz = obj.getClass();
		populate(user, obj, old, clazz, new Date());
	}
	private static Field getParentField(Class<?> clazz) {
		if (clazz == BaseObject.class)
			return null;
		Field[] fields = clazz.getDeclaredFields();
		if (!Myjung.isEmpty(fields)) {
			for (Field field : fields) {
				if (field.getName().equals("_parent"))
					return field;
			}
		}
		return getParentField(clazz.getSuperclass());
	}
	private static Field getField(Class<?> clazz, Class<?> type) {
		if (clazz == BaseObject.class)
			return null;
		Field[] fields = clazz.getDeclaredFields();
		if (!Myjung.isEmpty(fields)) {
			for (Field field : fields) {
				if (!field.getName().equals("_parent") && toGenericType(field) == type)
					return field;
			}
		}
		return getField(clazz.getSuperclass(), type);
	}
	@SuppressWarnings("unchecked")
	private void populateParent(String user, BaseObject parent, BaseObject child) throws Exception {
		Field field = getField(parent.getClass(), child.getClass());
		if (field == null || Set.class.isAssignableFrom(field.getType()))
			return;
		if (!field.isAccessible())
			field.setAccessible(true);
		if (List.class.isAssignableFrom(field.getType())) {
			List<BaseObject> children = (List<BaseObject>) field.get(parent);
			if (children == null) {
				children = new ArrayList<BaseObject>();
				field.set(parent, children);
			} else if (children.contains(child)) {
				return;
			}
			IdGenerator.populateId(child);
			children.add(child);
			update(user, parent, null);
		} else if (field.getType().isAssignableFrom(child.getClass())) {
			BaseObject pChild = (BaseObject) field.get(parent);
			if (pChild != null && pChild.equals(child))
				return;
			if (pChild != null) {
				Field pField = getParentField(pChild.getClass());
				if (pField != null) {
					if (!pField.isAccessible())
						pField.setAccessible(true);
					pField.set(pChild, null);
				}
			}
			IdGenerator.populateId(child);
			field.set(parent, child);
			update(user, parent, null);
		}
	}
	private void populate(String user, BaseObject obj, BaseObject old, Class<?> clazz, Date date) throws Exception {
		if (clazz == null || clazz == BaseObject.class)
			return;
		populate(user, obj, old, clazz.getSuperclass(), date);

		Field[] fields = clazz.getDeclaredFields();
		for (Field field : fields) {
			if (field.getName().equals("_parent")) {
				if (!field.isAccessible())
					field.setAccessible(true);
				Object parent = field.get(obj);
				if (parent == null) {
					if (old == null)
						continue;
					Object oldParent = field.get(old);
					if (oldParent == null)
						continue;

					populateParent(user, (BaseObject) oldParent, null);
				} else {
					populateParent(user, (BaseObject) parent, obj);
				}
				continue;
			}
			Annotation[] anns = field.getAnnotations();
			if (Myjung.isEmpty(anns))
				continue;
			for (Annotation ann : anns) {
				if (ann instanceof MdlAttribute) {
					MdlAttribute fAnn = (MdlAttribute) ann;
					if (fAnn.type().equals("creator")) {
						populateCreator(obj, field, user, old);
					} else if (fAnn.type().equals("creationDate")) {
						populateCreationDate(obj, field, date, old);
					} else if (fAnn.type().equals("modifier")) {
						populateModifier(obj, field, user);
					} else if (fAnn.type().equals("modificationDate")) {
						populateModificationDate(obj, field, date);
					}
				} else if (ann instanceof MdlElement) {
					MdlElement fAnn = (MdlElement) ann;
					if (fAnn.type().equals("creator")) {
						populateCreator(obj, field, user, old);
					} else if (fAnn.type().equals("creationDate")) {
						populateCreationDate(obj, field, date, old);
					} else if (fAnn.type().equals("modifier")) {
						populateModifier(obj, field, user);
					} else if (fAnn.type().equals("modificationDate")) {
						populateModificationDate(obj, field, date);
					}
				} else if (ann instanceof MdlText) {
					MdlText fAnn = (MdlText) ann;
					if (fAnn.type().equals("creator")) {
						populateCreator(obj, field, user, old);
					} else if (fAnn.type().equals("creationDate")) {
						populateCreationDate(obj, field, date, old);
					} else if (fAnn.type().equals("modifier")) {
						populateModifier(obj, field, user);
					} else if (fAnn.type().equals("modificationDate")) {
						populateModificationDate(obj, field, date);
					}
				} else if (ann instanceof MdlField) {
					MdlField fAnn = (MdlField) ann;
					if (fAnn.type().equals("creator")) {
						populateCreator(obj, field, user, old);
					} else if (fAnn.type().equals("creationDate")) {
						populateCreationDate(obj, field, date, old);
					} else if (fAnn.type().equals("modifier")) {
						populateModifier(obj, field, user);
					} else if (fAnn.type().equals("modificationDate")) {
						populateModificationDate(obj, field, date);
					}
				}
			}
		}
	}
	private static void populateCreator(BaseObject obj, Field field, String user, BaseObject old) throws Exception {
		if (!field.isAccessible())
			field.setAccessible(true);
		if (old == null)
			field.set(obj, user);
		else
			field.set(obj, field.get(old));
	}
	private static void populateCreationDate(BaseObject obj, Field field, Date date, BaseObject old) throws Exception {
		if (!field.isAccessible())
			field.setAccessible(true);
		if (old == null)
			field.set(obj, date);
		else
			field.set(obj, field.get(old));
	}
	private static void populateModifier(BaseObject obj, Field field, String user) throws Exception {
		if (!field.isAccessible())
			field.setAccessible(true);
		field.set(obj, user);
	}
	private static void populateModificationDate(BaseObject obj, Field field, Date date) throws Exception {
		if (!field.isAccessible())
			field.setAccessible(true);
		field.set(obj, date);
	}

	private static final List<Character> EMPTY_CHAR_LIST = ValueUtils.toList(' ', ')', '\r', '\n', '\t');
	@SuppressWarnings("unchecked")
	private static List<?> executeQuery(org.hibernate.Query query, QueryParameters parameters, Class<?> resultModel, List<String> fieldList) throws Exception {
		// TODO resultModel
		//		if (resultModel != null)
		//			query.setResultTransformer(Transformers.aliasToBean(resultModel));
		Map<String, Object> paramMap = parameters == null ? null : parameters.getParameterMap();
		if (!Myjung.isEmpty(paramMap)) {
			String queryStr = query.getQueryString();
			for (String key : paramMap.keySet()) {

				String nextStr = queryStr;
				boolean found = false;
				do {
					int i = nextStr.indexOf(new StringBuffer(":").append(key).toString());
					if (i == -1)
						break;
					int length = i + 1 + key.length();
					if (nextStr.length() <= length) {
						found = true;
						break;
					}
					nextStr = nextStr.substring(length);
					char nextChar = nextStr.charAt(0);
					if (!EMPTY_CHAR_LIST.contains(nextChar))
						continue;
					found = true;
				} while (!found);
				if (!found)
					continue;

				Object val = paramMap.get(key);
				if (val instanceof String)
					query.setString(key, (String) val);
				else if (val instanceof Boolean)
					query.setBoolean(key, (Boolean) val);
				else if (val instanceof Date)
					query.setDate(key, (Date) val);
				else if (val instanceof Integer)
					query.setInteger(key, (Integer) val);
				else if (val instanceof Long)
					query.setLong(key, (Long) val);
				else if (val instanceof Float)
					query.setFloat(key, (Float) val);
				else if (val instanceof Double)
					query.setDouble(key, (Double) val);
				else if (val instanceof Character)
					query.setCharacter(key, (Character) val);
				else
					query.setParameter(key, val);
			}

			if (paramMap.containsKey("pageSize") && paramMap.containsKey("pageNo"))
				populatePagination(query, Myjung.toInteger(paramMap.get("pageSize"), -1), Myjung.toInteger(paramMap.get("pageNo"), 0));
		}
		List<?> list = query.list();
		if (Myjung.isEmpty(fieldList) || Myjung.isEmpty(list))
			return list;

		boolean singleField = fieldList.size() == 1;
		if (singleField && list.get(0) instanceof BaseObject)
			return list;

		String firstFieldName = fieldList.get(0);

		if (resultModel == null) {
			List<TransferObject> resList = new ArrayList<TransferObject>(list.size());
			for (Object values : (List<Object>) list) {
				TransferObject res = new TransferObject();
				if (singleField) {
					res.setParameter(firstFieldName, values);
				} else {
					int i = 0;
					Object[] fieldObjs = (Object[]) values;
					for (String field : fieldList)
						res.setParameter(field, fieldObjs[i++]);
				}
				resList.add(res);
			}

			return resList;
		} else if (singleField && resultModel.isAssignableFrom(list.get(0).getClass())) {
			return list;
		}

		List<Object> resList = new ArrayList<Object>(list.size());
		Field firstField = singleField ? Myjung.getField(resultModel, firstFieldName) : null;
		for (Object values : (List<Object>) list) {
			Object res = resultModel.newInstance();
			if (singleField) {
				if (firstField == null)
					break;
				firstField.set(res, values);
			} else {
				int i = 0;
				Object[] fieldObjs = (Object[]) values;
				for (String fieldName : fieldList)
					Myjung.setValue(res, fieldName, fieldObjs[i++]);
			}
			resList.add(res);
		}
		return resList;
	}
	private List<String> toQueryFieldList(String query) {
		String lcQuery = query.toLowerCase();
		int selectIndex = lcQuery.indexOf("select");
		int fromIndex = lcQuery.indexOf("from");
		if (selectIndex == -1 || selectIndex > fromIndex)
			return null;
		List<String> fieldList = new ArrayList<String>();
		String fieldsStr = query.substring(selectIndex + 6, fromIndex);
		String[] fields = StringUtils.tokenizeToStringArray(fieldsStr, ",");
		for (String field : fields) {
			field = getLastString(field, " ");
			field = getLastString(field, "\t");
			field = getLastString(field, "\r\n");
			field = getLastString(field, ".");
			field = StringUtils.replace(field, ")", "");
			fieldList.add(field);
		}
		return fieldList;
	}
	protected List<?> executeQuery(org.hibernate.Query query, QueryParameters parameters, Class<?> resultClass) throws Exception {
		List<String> fieldList = null;
		fieldList = toQueryFieldList(query.getQueryString());
		// TODO resultClass
		//		if (resultClass == null)
		//			fieldList = toQueryFieldList(hql);
		return executeQuery(query, parameters, resultClass, fieldList);
	}
	/**
	 * Executes HQL query
	 * 
	 * @param hql
	 *            An HQL query to execute
	 * @param parameters
	 *            The parameters which is in the sql parameter as :paramName
	 *            format
	 * @param resultClass
	 *            The result object data type of this query
	 * @return
	 * @throws Exception
	 */
	public List<?> executeHqlQuery(String hql, QueryParameters parameters, Class<?> resultClass) throws Exception {
		org.hibernate.Query query = getSessionFactory().getCurrentSession().createQuery(hql);
		return executeQuery(query, parameters, resultClass);
	}
	/**
	 * Executes SQL query
	 * 
	 * @param sql
	 *            An SQL query to execute
	 * @param parameters
	 *            The parameters which is in the sql parameter as :paramName
	 *            format
	 * @param resultClass
	 *            The result object data type of this query
	 * @return
	 * @throws Exception
	 */
	public List<?> executeSqlQuery(String sql, QueryParameters parameters, Class<?> resultClass) throws Exception {
		SQLQuery query = getSessionFactory().getCurrentSession().createSQLQuery(sql);
		return executeQuery(query, parameters, resultClass);
	}
	private static String getLastString(String str, String deli) {
		if (str.indexOf(deli) == -1)
			return str;
		return str.substring(str.lastIndexOf(deli) + 1);
	}
	public <T> T delete(T obj) throws Exception {
		if (obj == null)
			return null;
		getHibernateTemplate().delete(obj);
		return obj;
	}
}